﻿#include<graphics.h>
#include<windows.h>
#include<iostream>
#include<conio.h>
#include<cstdio>
#include<cmath>
#include<ctime>
#include<io.h>
#undef UNICODE
#undef _UNICODE
#include "EasyXPng.h"
#include "GamesBuilder.h"
#include "Timer.h"
#pragma comment(lib,"Winmm.lib")
#define HEIGHT 800
#define WIDTH 1200
#define DEBUG 0 //是否开启控制台调试信息
#pragma warning(disable:4996)
using namespace std;

#pragma once
class Entity
{
public:
	float posx, posy, vx, vy; //实体坐标和速度 
};

class Block
{
public:
	int X1, X2, Y1, Y2;
};

class Wall : public Block
{
private:
public:
	int material;

	Wall();

	Wall(int X1, int Y1, int X2, int Y2, int material);

	void draw();
};

class Room
{
private:
public:

	int id;
	int built, wallNums;
	int go[5];
	vector<Wall> walls;

	Room(int id);

	void initRoom();

	void updateRoom();

	int isBuild();
};

class Map
{ //地图类
private:
public:
	int N; //房间个数
	vector<Room> rooms;

	void linkRoom();

	Map();

	Room getRoom(int n);

	vector<Room> getRooms();
};

class Creature : public Entity
{
public:
	float height, width;	//生物的高度和宽度
	int dirction, health, healthMax; //方向，血量,血量上限
	int inRoom;	//所在的房间
	int lastShootTime; //上次发射子弹的时间
	int shootCd; //发射CD
	int isRemoteAttack; //是否可以远程攻击(发射子弹)
	int bulletNums; // 单次发射的子弹数量
	int shootPower; //射出子弹的威力

	int changeX(int n);

	int changeY(int n);

	int isPassible();

	void Shoot(int X1, int Y1, int X2, int Y2, int from, int power);
};

class Bullet : public Entity
{
private:
public:
	IMAGE img;
	int radius, status, lifetime, power; //子弹的各属性 位置 速度 半径 是否还存活 剩余存活时间 子弹的威力
	int from;//子弹是谁打出来的 1 主角 2 怪物
	Bullet(float posx, float posy, float vx, float vy, int radius, int status, int lifetime, int from, int power);

	void draw();

	int getStatus();

	void update();

	int isCollideRocket(Creature creature);
};

class Player : public Creature
{
private:
public:
	float vy;//角色坐标的速度a
	int status; //角色状态 站立 - 1，跑动 - 2 举枪30-60度 - 3 举枪 60-90度 - 4 压枪 - 5
	void init(float height, float width, int posx, int posy, int healthMax, int health, int shootCd, int isRemoteAttack, int bulletNums, int shootPower);

	void draw();

	void update();

	void drawHeart();
};

class Enemy : public Creature//怪物的类
{
public:
	int kind, onFloor, fly;
	Enemy(float posx, float posy, int kind, int inRoom);

	void draw();

	void update();

	void updateVelforTarge();
};


const float g = 0.1;//重力加速度
const int WALL_SIZE = 10;
int tick = 1;
float posx_mouse, posy_mouse;//鼠标坐标
Map maps;
vector<Bullet> bullets;
Player hero;
vector<Enemy>monsters[233];//每个房间都有一个存怪物的vector
IMAGE heart_empty, heart_half, heart_full;//临时变量以后删 用来画血量


Wall::Wall(int X1, int Y1, int X2, int Y2, int material) {
	this->X1 = X1;
	this->X2 = X2;
	this->Y1 = Y1;
	this->Y2 = Y2;
	this->material = material;
}

void Wall::draw() {
	if (material == 1) imgBatchPrinter(1, X1, Y1, X2, Y2, 10, 10);
	if (material == 2) imgBatchPrinter(3, X1, Y1, X2, Y2, 45, 10);
	//画墙，因为直接调用会访问冲突，所以开了个函数画
}

Room::Room(int id)
{
	this->id = id;
	walls.reserve(32); //预分配内存，提速，防止溢出
	for (int i = 0; i < 4; ++i)go[i] = -1;
}

void Room::initRoom() //一个房间里面造墙
{
	walls.push_back(Wall(0, 0, 1200, 10, 1)); //天花板
	walls.push_back(Wall(0, 790, 1200, 800, 1)); //地板
	walls.push_back(Wall(0, 200, 20, 600, 1)); //左
	walls.push_back(Wall(1180, 200, 1200, 600, 1)); //右
	if (go[0] < 0) walls.push_back(Wall(0, 0, 20, 600, 1));//左上
	if (go[1] < 0) walls.push_back(Wall(0, 200, 20, 800, 1));//左下
	if (go[2] < 0) walls.push_back(Wall(1180, 0, 1200, 600, 1));//右上
	if (go[3] < 0) walls.push_back(Wall(1180, 200, 1200, 800, 1));//右下


	for (int i = 50; i < 700; i += (rand() % 50) + 100)//随机造一些墙
	{
		int t = (rand() % 4) + 2;
		while (t--)
		{
			int j = (rand() % 800) + 100;
			walls.push_back(Wall(j, i, j + (rand() % 100) + 100, i + 10, 2));
			j += (rand() % 100) + 100;
		}
	}
	built = 1;
}

void Room::updateRoom()
{ //更新房间
	for (auto it = walls.begin(); it != walls.end(); it++)
	{ //把房间里的墙全部画出来
		it->draw();
	}
}

int Room::isBuild()
{ //获取是否初始化完毕
	return  built;
}

void Map::linkRoom()//连接房间
{
	rooms.reserve(15);
	N = rand() % 7 + 5;
	Room r(0); rooms.push_back(r);
	for (int i = 1, to, ok; i < N; ++i)//生成一棵树
	{
		ok = 0; Room r(i);
		while (1)
		{
			to = rand() % i;
			for (int j = 0; j < 4; ++j)
				if (rooms[to].go[j] == -1)
				{
					ok = 1;
					rooms[to].go[j] = i;
					r.go[3 - j] = to;
					break;
				}
			if (ok)break;
		}
		rooms.push_back(r);
	}
	for (int i = 0; i < N; ++i)rooms[i].initRoom();
}

Map::Map()//地图生成器 
{
	linkRoom();
}

Room Map::getRoom(int n)
{ //根据id获取房间的实体
	if (n > N - 1 || n < 0)
	{
		return NULL;
	}
	else
	{
		return rooms[n];
	}
}

vector<Room> Map::getRooms() { //返回所有的房间
	return rooms;
}

int Creature::changeX(int n) //x坐标的变动 能动返回1 否则返回0
{
	posx += n;
	if (!isPassible())
	{ //判断是否合法
		posx -= n;
		return 0;
	}
	return 1;
}

int Creature::changeY(int n) //y坐标的变动 能动返回1 否则返回0
{
	posy += n;
	if (!isPassible())
	{ //判断是否合法
		posy -= n;
		return 0;
	}
	return 1;
}

int Creature::isPassible()//生物位置变动后是否合法（不和墙相交就是合法）
{
	Room k = maps.getRoom(inRoom); //获取房间
	for (auto it = k.walls.begin(); it != k.walls.end(); it++) { //迭代所有的墙
		if (collisionBetweenEntity(posx, posy - height,
			posx + width, posy,
			it->X1, it->Y1,
			it->X2, it->Y2))return 0; //有交界返回0
	}
	return 1;
}

void Creature::Shoot(int X1, int Y1, int X2, int Y2, int from, int power) {
	if (isRemoteAttack && clock() - lastShootTime > shootCd) {
		float tmpX = X1 + width / 2, tmpY = Y1 - height / 2; //计算子弹的各个属性
		float differenceX = X2 - tmpX, differenceY = Y2 - tmpY;
		Bullet k(tmpX, tmpY, differenceX / sqrt(differenceX * differenceX + differenceY * differenceY), differenceY / sqrt(differenceX * differenceX + differenceY * differenceY), 10, 1, 20000, from, power);
		bullets.push_back(k);
		lastShootTime = clock();
	}
}

Bullet::Bullet(float posx, float posy, float vx, float vy, int radius, int status, int lifetime, int from, int power)
{
	this->posx = posx;
	this->posy = posy;
	this->vx = vx;
	this->vy = vy;
	this->radius = radius;
	this->status = status;
	this->lifetime = lifetime;
	this->from = from;
	this->power = power;
	loadimage(&img, _T(".\\img\\material\\bullet\\bullet.png"), 2 * radius, 2 * radius);
}

void Bullet::draw()
{
	if (posx < 0 || posx > WIDTH || posy < 0 || posy > HEIGHT || lifetime <= 0)//如果子弹出界了就删去
	{
		status = 0;
	}
	if (status == 1)//否则画出来
	{
		putimagePng(posx, posy - 2 * radius, &img);
		lifetime--;
	}
}

int Bullet::getStatus() //获取子弹状态
{
	return status;
}

void Bullet::update() //更新子弹坐标
{
	posx += vx;
	posy += vy;
}

int Bullet::isCollideRocket(Creature creature) // 判断生物是否和子弹碰撞
{
	float distance_x = abs(creature.posx - posx);
	float distance_y = abs(creature.posy - posy);

	if (collisionBetweenEntity(posx, posy - 2 * radius, posx + 2 * radius, posy, creature.posx, creature.posy - creature.height, creature.posx + creature.width, creature.posy))return 1;
	else return 0;
}

void Player::init(float height, float width, int posx, int posy, int healthMax, int health, int shootCd, int isRemoteAttack, int bulletNums, int shootPower)
{
	this->height = height;
	this->width = width;
	this->posx = posx;
	this->posy = posy;
	this->healthMax = healthMax;
	this->health = health;
	this->shootCd = shootCd;
	this->isRemoteAttack = isRemoteAttack;
	this->bulletNums = bulletNums;
	this->shootPower = shootPower;
	status = 1;
	vy = 0;
}

void Player::draw() //根据状态返回对应的贴图
{
	if (status == 1)gifCreater("hero.stay", posx, posy - height, tick, width, height, dirction, 0.02);
	if (status == 2)gifCreater("hero.run", posx, posy - height, tick, width, height, dirction, 0.02);
	if (status == 3)gifCreater("hero.shoot.up_30_60", posx, posy - height, tick, width, height, dirction, 0.02);
	if (status == 4)gifCreater("hero.shoot.up_60_90", posx, posy - height, tick, width, height, dirction, 0.02);
	if (status == 5)gifCreater("hero.shoot.down", posx, posy - height, tick, width, height, dirction, 0.02);

	drawHeart(); //画血量ui
}

void Player::update()
{
	vy += g;
	if (!changeY(vy)) vy = 0;

	if (posx + width < 0)//向左进入其他房间
	{
		int toRoom;
		if (posy <= 400)//左上角
		{
			toRoom = maps.getRoom(inRoom).go[0];
		}
		else//左下角
		{
			toRoom = maps.getRoom(inRoom).go[1];
		}
		for (int i = 0; i < 4; i++)
		{
			if (maps.getRoom(toRoom).go[i] == inRoom) //找到传送过去是哪个门
			{
				posx = posTransformCoordinate(1, i, width);//位置转坐标
				posy = posTransformCoordinate(2, i, width);//位置转坐标
				inRoom = toRoom;
			}
		}
		bullets.clear();//进入其他房间就把子弹全清空
	}
	if (posx + width > WIDTH)//向右进入其他房间
	{
		int toRoom;
		if (posy <= 400)//右上角
		{
			toRoom = maps.getRoom(inRoom).go[2];
		}
		else//右下角
		{
			toRoom = maps.getRoom(inRoom).go[3];
		}
		for (int i = 0; i < 4; i++)
		{
			if (maps.getRoom(toRoom).go[i] == inRoom) { //找到传送过去是哪个门
				posx = posTransformCoordinate(1, i, width);//位置转坐标
				posy = posTransformCoordinate(2, i, width);//位置转坐标
				inRoom = toRoom;
			}
		}
		bullets.clear();//进入其他房间就把子弹全清空
	}
}

void Player::drawHeart()//画角色血量
{
	int nowx = 10;
	for (int i = 1; i <= health / 2; ++i)putimagePng(nowx, 20, &heart_full), nowx += 50;
	if (health & 1)putimagePng(nowx, 20, &heart_half), nowx += 50;
	for (int i = 1; i <= (healthMax - health) / 2; ++i)putimagePng(nowx, 20, &heart_empty), nowx += 50;
}

Enemy::Enemy(float posx, float posy, int kind, int inRoom)
{
	this->posx = posx;
	this->posy = posy;
	this->vx = 0;
	this->vy = 10;
	this->kind = kind;
	this->inRoom = inRoom;
	onFloor = 0;
	if (kind == 1)//每种怪物的属性
	{
		health = 3; width = 40; height = 40; fly = 0;
		shootCd = -1;
		isRemoteAttack = 0;
		bulletNums = 0;
	}
	if (kind == 2)//每种怪物的属性
	{
		health = 4; width = 60; height = 60; fly = 1;
		shootCd = 1000;
		isRemoteAttack = 1;
		bulletNums = 1;
	}
}

void Enemy::draw()
{
	if (kind == 1)
	{
		if (health > 0) gifCreater("nutscrab.run", posx, posy - 37, tick, width, height, dirction, 0.02);
	}
	if (kind == 2)
	{
		if (health > 0)
		{
			gifCreater("nutssnake.fly", posx, posy - 37, tick, width, height, dirction, 0.02);
		}
	}
}

void Enemy::update()
{
	//vx == 0 时不做修改
	if (vx > 0) {
		dirction = 0;
	}
	if (vx < 0) {
		dirction = 1;
	}
	if (fly)
	{
		updateVelforTarge();
		posx += vx;
		posy += vy;
	}
	else
	{
		vy = 5;
		if (!onFloor)//判断是否已经落地
		{
			if (!changeY(vy))
			{
				onFloor = 1;
				vx = 1;
			}
		}
		else
		{
			if (changeY(vy))
			{
				changeY(-vy);
				vx = -vx;
			}
		}
		if (!changeX(vx) || posx < 0 || posx > WIDTH) vx = -vx;
	}
	if (isRemoteAttack) {
		Shoot(posx, posy, hero.posx, hero.posy, 2, 1);
	}
}

void Enemy::updateVelforTarge() // 让会飞的怪的速度瞄向角色
{
	float scalar = (tick % 2); // 速度大小有一定的随机性
	if (hero.posx > posx) vx = scalar;// 目标在飞碟左边，飞碟x方向速度向右
	else if (hero.posx < posx) vx = -scalar;// 目标在飞碟右边，飞碟x方向速度向左
	if (hero.posy > posy)vy = scalar; // 目标在飞碟下方，飞碟y方向速度向下
	else if (hero.posy < posy)vy = -scalar; // 目标在飞碟上方，飞碟y方向速度向上
}


void makeMonster()//怪物生成器
{
	for (int i = 0; i < maps.N; ++i)
	{
		int numMonster = rand() % 5 + 3;
		for (int j = 1; j <= numMonster; ++j)
		{
			Enemy now(rand() % (WIDTH - 100) + 50, rand() % HEIGHT, (rand() % 2) + 1, i);
			Enemy tmp(rand() % WIDTH, rand() % HEIGHT, (rand() % 2) + 1, i);
			while (1)
			{
				Enemy tmp(rand() % WIDTH, rand() % HEIGHT, (rand() % 2) + 1, i);
				if (tmp.isPassible() == 1)
				{
					now = tmp;
					break;
				}
			}
			monsters[i].push_back(now);
		}
	}
}
void Init()//预处理
{
	initgraph(1200, 800, 1);
	setbkcolor(RGB(0, 0, 0));
	cleardevice();
	initImg(); //记载图片
	hero.init(90, 90, 20, 770, 10, 10, 300, 1, 1, 1);//角色初始化大小和坐标和血量
	hero.inRoom = 0;//初始房间是0号
	makeMonster();//造怪物
	//perfect_wall(0);//完善墙

	loadimage(&heart_empty, _T(".\\img\\material\\heart\\empty.png"), 50, 50);//临时变量以后删 用来画血量
	loadimage(&heart_half, _T(".\\img\\material\\heart\\half.png"), 50, 50);//临时变量以后删 用来画血量
	loadimage(&heart_full, _T(".\\img\\material\\heart\\full.png"), 50, 50);//临时变量以后删 用来画血量
}

void drawMonster()//画怪物
{
	int nowRoom = hero.inRoom;
	for (vector<Enemy>::iterator it = monsters[nowRoom].begin(); it != monsters[nowRoom].end(); ++it)it->draw();
}

void drawBullet()
{
	for (auto it = bullets.begin(); it != bullets.end(); it++) it->draw();
}

void draw_pos_room()
{
	LOGFONT f;
	TCHAR s[20];
	_stprintf(s, _T("%d"), hero.inRoom);
	gettextstyle(&f);						// 获取当前字体设置
	f.lfHeight = 100;						// 设置字体高度
	_tcscpy(f.lfFaceName, _T("SansTaiNa"));
	/*settextcolor(HSVtoRGB(bk_color,0.8,0.8));*/
	settextstyle(&f);
	outtextxy(550, 350, s);
}

void draw_death()//画死亡界面
{

	while (1)
	{
		BeginBatchDraw();
		setbkmode(TRANSPARENT);
		settextcolor(RGB(255, 0, 0));
		settextstyle(120, 0, _T("宋体"));
		outtextxy(400, 350, _T("游戏失败"));
		FlushBatchDraw();
	}

	exit(0);
}

void show()//画出所有部分 
{
	cleardevice();
	BeginBatchDraw();
	draw_pos_room();//画房间号码
	drawMonster();//画怪物
	drawBullet();//画子弹
	maps.getRoom(hero.inRoom).updateRoom();
	hero.draw();//画角色，位于最上的图层
	FlushBatchDraw();
}
void update_withoutinput()
{
	tick++;
	if (tick > 0x3f3f3f3f) tick = 1;

	hero.update();//对于HERO坐标更新

	int nowRoom = hero.inRoom;//怪物坐标的更新

	static float lastTime = 0;
	for (vector<Bullet>::iterator it1 = bullets.begin(); it1 != bullets.end();)//子弹的更新
	{
		it1->update();//子弹坐标的更新
		for (vector<Enemy>::iterator it2 = monsters[nowRoom].begin(); it2 != monsters[nowRoom].end(); ++it2)//子弹打到怪的更新
		{
			if (it1->from == 1 && it2->health > 0 && collisionBetweenEntity(it1->posx, it1->posy - 2 * it1->radius, it1->posx + 2 * it1->radius, it1->posy, it2->posx, it2->posy - it2->height, it2->posx + it2->width, it2->posy))//如果子弹碰到了怪
			{
				--it2->health;

				it1->status = 0;

				break;
			}
		}
		float nowTime = clock();
		if (nowTime - lastTime > 1000 && it1->from == 2 && collisionBetweenEntity(it1->posx, it1->posy - 2 * it1->radius, it1->posx + 2 * it1->radius, it1->posy, hero.posx, hero.posy - hero.height, hero.posx + hero.width,hero.posy))//如果子弹碰到了人
		{
			lastTime = nowTime;
			hero.health--;
			it1->status = 0;
			break;
		}
		if (it1->getStatus() == 0) it1 = bullets.erase(it1);
		else ++it1;
	}

	for (vector<Enemy>::iterator it = monsters[nowRoom].begin(); it != monsters[nowRoom].end();)//怪物的更新
	{
		it->update();//怪物坐标的更新
		if (it->health <= 0)it = monsters[nowRoom].erase(it);
		else ++it;
	}
	for (vector<Enemy>::iterator it = monsters[nowRoom].begin(); it != monsters[nowRoom].end(); ++it)//检测怪物是否碰到了人
	{
		float nowTime = clock();
		if (nowTime - lastTime > 1000 && collisionBetweenEntity(hero.posx, hero.posy - hero.height, hero.posx + hero.width, hero.posy, it->posx, it->posy - it->height, it->posx + it->width, it->posy))//如果子弹碰到了怪
		{//怪物碰到了人
			lastTime = nowTime;
			--hero.health;
			break;
		}
		if (hero.health <= 0)//角色死亡
		{
			draw_death();
			break;
		}
	}
}
void update_withinput()
{
	int flag = 1/*判断是否有操作，以此来更新角色状态图片*/;
	MOUSEMSG m;
	static int shoot = 0;//判断是否正在开枪
	static float lastShootTime = 0;//上次开枪时间
	if (MouseHit())//关于鼠标的操作 
	{
		m = GetMouseMsg();
		posx_mouse = m.x; posy_mouse = m.y;//鼠标位置

		if (m.uMsg == WM_LBUTTONDOWN)shoot = 1;//开枪了
		if (m.uMsg == WM_LBUTTONUP)shoot = 0;//不开枪了
		flag = 0;
	}

	if (shoot) {
		if (posx_mouse < hero.posx) hero.dirction = 1;/*枪口的朝向*/
		else hero.dirction = 0;
		if (abs(posx_mouse - hero.posx) < abs(posy_mouse - hero.posy)) hero.status = 4;
		else hero.status = 3;
		if (posy_mouse - hero.posy > 0) hero.status = 5;
	}

	if (shoot && clock() - lastShootTime > hero.shootCd)//按下了左键 发射子弹 调整这里的300就可以调整射速
	{
		float tmpX = hero.posx + hero.width / 2, tmpY = hero.posy - hero.height / 2;//计算子弹的各个属性
		float differenceX = posx_mouse - tmpX, differenceY = posy_mouse - tmpY;
		Bullet k(tmpX, tmpY, 10.0 * differenceX / sqrt(differenceX * differenceX + differenceY * differenceY), 10.0 * differenceY / sqrt(differenceX * differenceX + differenceY * differenceY), 10, 1, 50, 1, hero.shootPower);
		bullets.push_back(k);
		lastShootTime = clock();
	}

	if (GetKeyState(0x41) < 0)//按下了A
	{
		hero.changeX(-1);
		hero.status = 2;
		if (flag) hero.dirction = 1;
		flag = 0;
	}
	if (GetKeyState(0x44) < 0)//按下了D
	{
		hero.changeX(1);
		hero.status = 2;
		if (flag) hero.dirction = 0;
		flag = 0;
	}
	if (GetKeyState(0x57) < 0) hero.vy = -5;
	//perfect_wall(hero.inRoom);//完善墙
	if (flag && hero.status != 3 && hero.status != 4 && hero.status != 5) hero.status = 1; //没有操作，重置角色状态
}


int main()
{
	srand(time(0));

	mciSendString(_T("open .\\music\\music_1.mp3 alias bkmusic"), NULL, 0, NULL);//打开背景音乐
	mciSendString(_T("play bkmusic repeat"), NULL, 0, NULL);  // 循环播放

	Init();
	double dur;
	clock_t start = clock(), end = clock();
	Timer k;
	while (1)
	{
		show();//画
		update_withoutinput();//更新 与输入无关
		update_withinput();//更新 与输入有关
		/*下面用来测帧率*/
		end = clock();
		dur = (double)(end - start);
		if (tick % 10 == 0) printf("FPS: %f\n", 1 / (dur / CLOCKS_PER_SEC));
		k.Sleep(1);//根据机器性能自行调整
		start = clock();
	}
	_getch();
	closegraph();
	return 0;
}